Gather pages based on keywords
複数のページの1 hop linkをまとめて表示するUserScript
動作している様子↓
https://scrapbox.io/files/69bd0335b9e150750aec5b5d.mp4
説明
Gather pages based on keywordsの使い方
code:script.js
import {ScrapboxParser} from '/api/code/customize/scrapbox-parser.min.js/parser.js';
window.ScrapboxParser = ScrapboxParser;
main();
scrapbox.on('page:changed', main);
async function main() {
// 初期化する。ページを遷移した時に、それまで挿入していたページカードを消す
cleaner();
// 現在開いているページがスクリプトを実行すべきページかどうか確認する
if (!(await isThereList())) return;
// ページカードを表示する場所を作る。関連ページリストの一番上に作る。
createContainer();
// ページのデータを取得する
const pages = await fetchPages();
// 取得したデータにページが存在しないとき、関数を終了する
if (pages.length === 0) {
report('noPages');
console.log('1 hop pages collector: noPages')
return;
}
// ページのデータを並べ替える。関連ページリストのSortの順番に並べ替える
const sorted = sortedPages(pages);
// ページカードを陳列する
insertPageCards(sorted); //This function use createPageCard()
}
isThereList()
code:script.js
async function isThereList() {
const url = https://scrapbox.io/api/code/${scrapbox.Project.name}/${scrapbox.Page.title}/list
try {
const response = await fetch(url)
if (!response.ok) {
console.log("1 hop pages collector: This is not the page to run. There is no 'code:list'.")
return false;
}
const result = await response.json();
console.log("1 hop pages collector: This is the page to run. There is 'code:list'.")
return true;
} catch(error) {
console.log("1 hop pages collector: error");
return false
}
}
fetchPages()
code:script.js
async function fetchPages() {
console.log("1 hop pages collector: fetching...")
report('fetching')
const response = await fetch(https://scrapbox.io/api/code/${encodeURIComponent(scrapbox.Project.name)}/${encodeURIComponent(scrapbox.Page.title)}/list);
const result = await response.json();
//親ページのリストを作成する
const parentPages = [];
//それぞれのprefixについてsufix違いの文字列を生成し、parentPages Arrayに格納する
for (const prefix of result.prefixes) {
for (const suffix of result.suffixes) {
const string = ${prefix}${suffix}
parentPages.push(string);
}
}
console.log("1 hop pages collector: These are the parent pages you defined in the list.")
console.log(parentPages)
report('list', parentPages);
//子ページのリストを作成する
const pages = [];
//親ページにリンクしているページを集める
for (const parentPage of parentPages) {
const url = https://scrapbox.io/api/pages/s1wiki/${encodeURIComponent(parentPage)}
const response = await fetch(url)
const result = await response.json();
const page = result.relatedPages.links1hop;
pages.push(...page);
}
// console.log(links1hopArray);
return pages;
}
sortedPages()
code:script.js
function sortedPages(pages) {
console.log("1 hop pages collector: sorting...")
report('sorting')
const sortType = localStorage.relatedPageSort;
console.log(1 hop pages collector: sortType is ${sortType});
// report('sortType', sortType)
switch (sortType) {
case '"related"':
case '"updated"':
case '"created"':
case '"accessed"':
case '"linked"':
case '"pageRank"':
return pages.sort((a, b) => a.sortType - b.sortType);
case '"title"':
return pages.sort((a, b) => a.title.localeCompare(b.title));
default:
return pages.sort((a, b) => a.related - b.related);
}
return sortedPages;
}
createContainer()
code:script.js
function createContainer() {
const div = document.createElement('div');
div.classList.add('pageCardWrapper');
const ul = document.createElement('ul')
ul.classList.add('grid')
div.appendChild(ul)
const linksContainer = document.querySelector('.links-container');
linksContainer.prepend(div);
}
insertPageCards()
code:script.js
function insertPageCards(sorted) {
report('remove');
const insertPlace = document.querySelector('.pageCardWrapper ul.grid');
for (const page of sorted) {
const pageCard = createPageCard(page);
insertPlace.insertAdjacentHTML('beforeend', pageCard);
}
}
createPageCard()
この関数内で使われてる関数
parse()
parse()の中ではmarkupLine()が使われてる
markupLine()の中ではmarkupNode()が使われてる
code:script.js
function createPageCard({project=scrapbox.Project.name, title, descriptions, imageUrl=undefined} = {}) {
const content = imageUrl ?
`<div class="icon">
<img loading="lazy" src="${imageUrl}">
</div>` :
<div class="description">${parse(descriptions, project)}</div>;
return `
<li class="page-list-item grid-style-itme">
<a href="/${project}/${encodeURIComponent(title)}" rel="route">
<div class="hover"></div>
<div class="content">
<div class="header">
<div class="title">${title}</div>
</div>
${content}
</div>
</a>
</li>`;
console.log(element);
}
parse()
code:script.js
function parse(descriptions, project) {
const blocks = ScrapboxParser.parse(descriptions.join('\n'), {hasTitle: false});
return blocks.map(block => markupLine(block, project)).join('');
}
markupLine()
code:script.js
function markupLine(block, project) {
switch (block.type) {
case 'title': // タイトルは選択範囲に入らないので無視
case 'codeBlock':
case 'table':
return <p class="line">table:${block.fileName}</p><br>;
case 'line':
if (block.nodes.length === 0) return '';
if (block.nodes.length === 1) {
const node = block.nodes0;
if (node.type === 'blank') return '';
if (node.type === 'plain') return <p class="line">${node.text}</p><br>;
}
return <p class="line">${block.nodes.map(node => '<span>' + markupNode(node, project) +'</span>').join('')}</p><br>;
}
}
markupNode()
code:script.js
function markupNode(node, project) {
switch (node.type) {
case 'quote':
case 'image':
case 'strongImage':
case 'formula':
case 'googleMap':
return '';
case 'icon':
case 'strongIcon':
if (node.pathType ==='relative') {
return <img class="inline-icon" src="https://scrapbox.io/api/pages/${project}/${node.path}/icon">;
}
return <img class="inline-icon" src="https://scrapbox.io/api/pages${node.path}/icon">;
case 'strong':
return <strong>${node.text}</strong>;
case 'decoration':
let result = node.nodes.map(node => convertNode(node)).join('');
if (node.decos.includes('/')) result = <i>${result}</i>;
if (node.decos.some(deco => /\*-/.test(deco0))) result = <strong>${result}</strong>;
if (node.decos.some(deco => deco0 === '~')) result = <strike>${result}</strike>;
return result;
case 'helpfeel':
return <code>? ${node.text}</code>;
case 'code':
return <code><span>${node.text}</span></code>;
case 'commandLine':
return <code>${node.symbol} ${node.text}</code>;
case 'link':
switch(node.pathType) {
case 'root':
return <span class="page-link">${node.href}<span>;
case 'relative':
return <span class="page-link">${node.href}</span>;
case 'absolute':
return <span class="link">${node.content ?? node.href}</span>;
}
case 'hashTag':
return <span class="page-link">${node.href}<span>;
case 'blank':
case 'plain':
return node.text;
}
}
containerに書き込むやつ()
code:script.js
function report(context, data) {
const insertPlace = document.querySelector('div.pageCardWrapper')
switch (context) {
case 'fetching':
const pFetching = <p style="color: white;">fetching...<p>;
insertPlace.insertAdjacentHTML('beforeend', pFetching);
break;
case 'list':
const pList = <p style="color: white;">The parent pages you defined in the list: ${data.join(', ')}<p>;
insertPlace.insertAdjacentHTML('beforeend', pList);
break;
case 'sorting':
const pSorting = <p style="color: white;">sorting...<p>;
insertPlace.insertAdjacentHTML('beforeend', pSorting);
break;
case 'sortType':
const pSortType = <p style="color: white;">sortType: ${data}<p>;
insertPlace.insertAdjacentHTML('beforeend', pSortType);
break;
case 'noPages' :
const pNoPages = <p style="color: white;">There are no pages to display here<p>;
insertPlace.insertAdjacentHTML('beforeend', pNoPages);
break;
case 'remove':
const ul = document.createElement('ul');
ul.classList = 'grid';
insertPlace.replaceChildren(ul);
break;
}
}
cleaner()
code:script.js
function cleaner() {
const wrapper = document.querySelector('div.pageCardWrapper');
if (wrapper) wrapper.remove();
}
謝辞
次の2つのコードを使用しています
/takker/scrapboxのページカードを作成するscript
取得したページ情報からページカードを生成するプログラム
scrapbox-Parser
scrapbox-parser.min.jsのページのものを使っています
これらがなかったら完成しませんでした
ありがとうございます
追加した要素をdocumenから削除するやつ()
初期化する
こういうのが必要だっけ?
不要かも
ページを遷移するとページカードリストは消えてる
対応するページがなかったとき